// TODO: Tail choice optimization

class Prolog {
    Stack<Choicepoint> choicepoints;
    Module[] modules;
    Unifier unifier;
    
    public Prolog(Term query) {
        choicepoints = new Stack<Choicepoint>
            (new Choicepoint(new Predicate(query, 0, 1)),
             null);
        modules = new Module[]
            {new BookModule(),
             new JavaGlue()};
        unifier = new Unifier();
    }
    
    public boolean resolve() {
        while(choicepoints != null) {
            if(choicepoints.head.finished) {
                choicepoints.head.undoBindings();
                choicepoints = choicepoints.tail;
            }
            else if(choicepoints.head.query == null) {
                choicepoints.head.finished = true;
                return true;
            }
            else {
                choicepoints.head = choicepoints.head.withIncrementedChoice();
                
                Conjunct currentConjunct = choicepoints.head.query.head;
                if(currentConjunct.kind == Conjunct.Kind.PREDICATE) {
                    Predicate currentPredicate = (Predicate)currentConjunct;
                    Compound currentFunctor = (Compound)currentPredicate.term.deref();
                    
                    //System.out.println("%% " + currentFunctor +"!" +"   @"+ currentConjunct.choice +"/"+ choicepoints.height);
                    
                    boolean found = false;
                    for(int i = 0; i < modules.length; i++) {
                        choicepoints = modules[i].tryToContinue
                            (currentFunctor,currentConjunct.choice,choicepoints,unifier);
                        if(modules[i].status == Module.Status.Success) {
                            //System.out.println(" % success");
                            found = true;
                            break;
                        }
                        else if(modules[i].status == Module.Status.TryAgain) {
                            //System.out.println(" % try_again("+currentFunctor.name +"/"+ currentFunctor.arity+")");
                            choicepoints.head = choicepoints.head.withIncrementedChoice();
                            currentConjunct = choicepoints.head.query.head;
                            i--;
                            continue;
                        }
                        else if(modules[i].status == Module.Status.ThatsAll) {
                            //System.out.println(" % thats_all");
                            found = true;
                            choicepoints.head.finished = true;
                            break;
                        }
                        else if(modules[i].status == Module.Status.NotFound) {
                            continue;
                        }
                    }
                    if(!found) {
                        choicepoints.head.finished = true;
                        System.out.println("%% " + currentFunctor.name +"/"+ currentFunctor.arity +"?");
                    }
                }
                else if(currentConjunct.kind == Conjunct.Kind.JAVA) {
                    if(((JavaCall)currentConjunct).call()) {
                        choicepoints.head.query = choicepoints.head.query.tail;
                    }
                    else {
                        choicepoints.head.finished = true;
                        return false;
                    }
                }
            }
        }
            
        return false;
    }
    
    public static void main(String[] args) {
        Variable e = new Variable();
        Term query = new Compound("member",
          e,
          new Compound(".", new Compound("a"),
          new Compound(".", new Compound("b"),
          new Compound(".", new Compound("c"),
          new Compound(".", new Compound("d"),
          new Compound(".", new Compound("e"),
          new Compound("[]")))))));
        //query = new Compound(args[0]);
        query = new Compound("example");
        Prolog interpreter = new Prolog(query);
        while(interpreter.resolve()) System.out.println("yes.");
    }
}
